﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Web;
using System.Drawing;
using System.Drawing.Imaging;
using Telerik.Web.UI.Widgets;
using ITM.Modules.SharePointFileExplorer.SharePoint;
using DotNetNuke.Entities.Users;
using DotNetNuke.Security;
using DotNetNuke.Entities.Host;

namespace ITM.Modules.SharePointFileExplorer
{
    public class SPContentProvider : FileBrowserContentProvider
    {
        // constants
        private const string SPUSER = "SPUserName";
        private const string SPPASSWORD = "SPPassword";
        private string _sharepointUrl;

        // user infos for SharePoint 
        private string _encryptSPUserName;
        private string _encryptSPPassword;
        private SharePointController _sharePointController;
        private Dictionary<string, string> _documentLibraryDictionary;
        private string _sessionedSPDomain;

        // !! need to consider here again !! 
        private string _itemHandlerPath = "DesktopModules/SharePointFileExplorer/FileExplorerHandler.ashx";

        // properties
        private string ItemHandlerPath
        {
            get
            {
                return _itemHandlerPath;
            }
        }

        private string SharePointUrl
        {
            get
            {
                return _sharepointUrl;
            }
        }

        private string EncryptSPUserName
        {
            get
            {
                return _encryptSPUserName;
            }
        }

        private string EncryptSPPassword
        {
            get
            {
                return _encryptSPPassword;
            }
        }

        private string SessionedSPDomain
        {
            get
            {
                return _sessionedSPDomain;
            }
        }

        public SharePointController SharePointContorller
        {
            get
            {
                if (_sharePointController == null)
                {
                    // get user infos of SharePoint
                    PortalSecurity portalSecurity = new PortalSecurity();
                    string username = portalSecurity.Decrypt(Host.GUID, EncryptSPUserName);
                    string password = portalSecurity.Decrypt(Host.GUID, EncryptSPPassword);
                    _sharePointController = new SharePointController(username, password, SessionedSPDomain, SharePointUrl);
                }
                return _sharePointController;
            }
        }

        public Dictionary<string, string> DocumentLibraryDictionary
        {
            get
            {
                if (_documentLibraryDictionary == null)
                {
                    _documentLibraryDictionary = SharePointContorller.SPGetDocumentLibraryProperties();
                }
                return _documentLibraryDictionary;
            }
        }

        public SPContentProvider(HttpContext context, string[] searchPatterns, string[] viewPaths, string[] uploadPaths, string[] deletePaths, string selectedUrl, string selectedItemTag)
            : base(context, searchPatterns, viewPaths, uploadPaths, deletePaths, selectedUrl, selectedItemTag)
        {
            // here need to get url 
            // viewPaths: url/documentlibrary
            if (viewPaths[0] != null)
                this._sharepointUrl = viewPaths[0].Substring(0, viewPaths[0].LastIndexOf('/') + 1);
            // process in order to get user infos of SharePoint
            UserInfo currentUserInfo = UserController.GetCurrentUserInfo();
            if (string.IsNullOrEmpty(currentUserInfo.Profile.GetPropertyValue(SPUSER)) || string.IsNullOrEmpty(currentUserInfo.Profile.GetPropertyValue(SPPASSWORD)))
            {
                if (context.Session["TempSPUserName"] != null && context.Session["TempSPPassword"] != null)
                {
                    this._encryptSPUserName = ((Dictionary<string, string>)context.Session["TempSPUserName"])[currentUserInfo.Username];
                    this._encryptSPPassword = ((Dictionary<string, string>)context.Session["TempSPPassword"])[currentUserInfo.Username];
                }
            }
            else
            {
                this._encryptSPUserName = currentUserInfo.Profile.GetPropertyValue(SPUSER);
                this._encryptSPPassword = currentUserInfo.Profile.GetPropertyValue(SPPASSWORD);
            }
            this._sessionedSPDomain = (string)context.Session["TempSPDomain"];
        }

        public override bool CheckDeletePermissions(string folderPath)
        {
            return true;
        }

        public override bool CheckWritePermissions(string folderPath)
        {
            return true;
        }

        public override string CreateDirectory(string path, string name)
        {
            if (!path.EndsWith("/"))
                path += "/";
            string documentLibraryName = GetDocumentLibraryName(path);
            List<string> folderList = SharePointContorller.SPGetFolders(documentLibraryName, DocumentLibraryDictionary[documentLibraryName], ProcessRelativePath(path));
            if (folderList.Contains(name))
                return name + " already exists...";
            else
            {
                string folderRelativePath = ProcessRelativePath(path + name);
                if (folderRelativePath.EndsWith("/"))
                    folderRelativePath = folderRelativePath.TrimEnd('/');
                try
                {
                    SharePointContorller.SPCreateFolder(folderRelativePath);
                    // if operation is successful then return string.Empty
                    return string.Empty;
                }
                catch (Exception ex)
                {
                    return "CreateDirectory failed: " + ex.Message;
                }
            }
        }

        /// <summary>
        /// helper function
        /// ex: Url/documentlibrary/folder/subfolder/file or documentlibrary/folder/subfolder/file
        /// return value should be: documentlibrary
        /// </summary>
        /// <param name="path"></param>
        /// <returns></returns>
        private string GetDocumentLibraryName(string path)
        {
            // check whether it contains Url 
            if (path.Contains(SharePointUrl))
                path = path.TrimStart(SharePointUrl.ToCharArray());
            return path.Split('/')[0];
        }

        /// <summary>
        /// helper function
        /// before process: url/documentlibrary/folder/subfolder/
        /// after process: documentlibraryviewname/folder/subfolder/
        /// </summary>
        /// <param name="path"></param>
        private string ProcessRelativePath(string path)
        {
            if (path.Contains(SharePointUrl))
                path = path.TrimStart(SharePointUrl.ToCharArray());
            string[] strArray = path.Split('/');
            string documentLibraryView = DocumentLibraryDictionary[strArray[0]];
            strArray[0] = documentLibraryView;
            string relativePath = string.Empty;
            foreach (var str in strArray)
            {
                if ((str != "") && (str != null))
                    relativePath += str + "/";
            }
            return relativePath;
        }

        /// <summary>
        /// Delete a directory
        /// </summary>
        /// <param name="path"></param>
        /// <returns></returns>
        public override string DeleteDirectory(string path)
        {
            string folderRelativePath = ProcessRelativePath(path);
            if (folderRelativePath.EndsWith("/"))
                folderRelativePath = folderRelativePath.TrimEnd('/');
            string documentLibrary = GetDocumentLibraryName(path);
            List<string> fileList = SharePointContorller.SPGetFiles(documentLibrary, DocumentLibraryDictionary[documentLibrary], folderRelativePath);
            List<string> folderList = SharePointContorller.SPGetFolders(documentLibrary, DocumentLibraryDictionary[documentLibrary], folderRelativePath);
            if (fileList.Count > 0 || folderList.Count > 0)
            {
                return "Please remove files/fub-folders before deleting folder...";
            }
            else
            {
                try
                {
                    SharePointContorller.SPDeleteFolder(folderRelativePath);
                    return string.Empty;
                }
                catch (Exception ex)
                {
                    return "DeleteDirectory failed: " + ex.Message;
                }
            }
        }

        /// <summary>
        /// Delete a file 
        /// </summary>
        /// <param name="path"></param>
        /// <returns></returns>
        public override string DeleteFile(string path)
        {
            try
            {
                SharePointContorller.SPDeleteFile(GetDocumentLibraryName(path), (SharePointUrl + ProcessRelativePath(path)).TrimEnd('/'));
                return string.Empty;
            }
            catch (Exception ex)
            {
                return "DeleteFile failed: " + ex.Message;
            }
        }

        /// <summary>
        /// Get file content
        /// </summary>
        /// <param name="url"></param>
        /// <returns></returns>
        public override Stream GetFile(string url)
        {
            try
            {
                byte[] fileContent = SharePointContorller.SPGetFileContent((SharePointUrl + ProcessRelativePath(url)).TrimEnd('/'));
                if (fileContent != null)
                    return new MemoryStream(fileContent);
                return null;
            }
            catch
            {
                throw new Exception("GetFile failed");
            }
        }

        /// <summary>
        /// Get file name
        /// </summary>
        /// <param name="url"></param>
        /// <returns></returns>
        public override string GetFileName(string url)
        {
            if (url.EndsWith("/"))
                url = url.TrimEnd('/');
            string fileName = url.Substring(url.LastIndexOf('/') + 1);
            // not sure whether all files end with extension type so just return
            return fileName;
        }

        /// <summary>
        /// Get relative path of parent folder
        /// ex. /folder/subfolder/file
        /// retrun-value should be /folder/subfolder/
        /// dont forget the last /
        /// </summary>
        /// <param name="url"></param>
        /// <returns></returns>
        public override string GetPath(string url)
        {
            return url.Substring(0, url.LastIndexOf('/') + 1);
        }

        public override string MoveFile(string path, string newPath)
        {
            string oldFilePath = path.Substring(0, path.LastIndexOf('/'));
            string newFilePath = newPath.Substring(0, newPath.LastIndexOf('/'));
            if (!oldFilePath.Contains(SharePointUrl))
            {
                oldFilePath = SharePointUrl + oldFilePath;
                path = SharePointUrl + path;
            }
            if (!newFilePath.Contains(SharePointUrl))
            {
                newFilePath = SharePointUrl + newFilePath;
                newPath = SharePointUrl + newPath;
            }
            try
            {
                // documentlibrary/folder/subfolder/file1
                // documentlibrary/folder/subfolder/file2
                string processedAbsoluteFilePath = (SharePointUrl + ProcessRelativePath(path)).TrimEnd('/');
                string documentLibrary = GetDocumentLibraryName(path);
                if (oldFilePath == newFilePath)
                {
                    // rename file 
                    string newFileName = newPath.Substring(newPath.LastIndexOf('/') + 1);
                    SharePointContorller.SPRenameFile(documentLibrary, DocumentLibraryDictionary[documentLibrary], processedAbsoluteFilePath, newFileName);
                    return string.Empty;
                }
                else
                {
                    // move file between different folders
                    // first copy to destination path
                    string[] destPaths = { (SharePointUrl + ProcessRelativePath(newPath)).TrimEnd('/') };
                    SharePointContorller.SPCopyFileLocal(processedAbsoluteFilePath, destPaths);
                    // then delete old file
                    SharePointContorller.SPDeleteFile(documentLibrary, processedAbsoluteFilePath);
                    return string.Empty;
                }
            }
            catch (Exception ex)
            {
                return "MoveFile failed " + ex.Message;
            }
        }

        /// <summary>
        /// Move a directory
        /// </summary>
        /// <param name="path"></param>
        /// <param name="newPath"></param>
        /// <returns></returns>
        public override string MoveDirectory(string path, string newPath)
        {
            string documentLibraryName = GetDocumentLibraryName(path);
            string relativePath = ProcessRelativePath(path);
            string relativeNewPath = ProcessRelativePath(newPath);
            if (relativePath.EndsWith("/"))
                relativePath = relativePath.TrimEnd('/');
            if (relativeNewPath.EndsWith("/"))
                relativeNewPath = relativeNewPath.TrimEnd('/');
            string folderName = relativePath.Substring(relativePath.LastIndexOf('/') + 1);
            string relativeParentPath = relativePath.Substring(0, relativePath.LastIndexOf('/') + 1);
            string relativeParentNewPath = relativeNewPath.Substring(0, relativeNewPath.LastIndexOf('/') + 1);
            try
            {
                if (relativeParentPath == relativeParentNewPath)
                {
                    string newFolderName = relativeNewPath.Substring(relativeNewPath.LastIndexOf('/') + 1);
                    if (folderName == newFolderName)
                        return folderName + " already exists...";
                    else
                    {
                        SharePointContorller.SPRenameFolder(documentLibraryName, DocumentLibraryDictionary[documentLibraryName], SharePointUrl + relativePath, newFolderName);
                        return string.Empty;
                    }
                }
                else
                {
                    SharePointContorller.SPMoveFolderLocal(documentLibraryName, DocumentLibraryDictionary[documentLibraryName], folderName, SharePointUrl + relativeParentPath, SharePointUrl + relativeParentNewPath);
                    return string.Empty;
                }
            }
            catch (Exception ex)
            {
                return "MoveDirectory failed " + ex.Message;
            }
        }

        /// <summary>
        /// Copy a file from old path to new path
        /// </summary>
        /// <param name="path"></param>
        /// <param name="newPath"></param>
        /// <returns></returns>
        public override string CopyFile(string path, string newPath)
        {
            // process paths
            string[] destPaths = { (SharePointUrl + ProcessRelativePath(newPath)).TrimEnd('/') };
            string processedPath = (SharePointUrl + ProcessRelativePath(path)).TrimEnd('/');
            // check file exists or not
            if (processedPath == destPaths[0])
                return processedPath.Substring(processedPath.LastIndexOf('/') + 1) + " already exists...";
            else
            {
                try
                {
                    SharePointContorller.SPCopyFileLocal(processedPath, destPaths);
                    return string.Empty;
                }
                catch (Exception ex)
                {
                    return "CopyFile failed " + ex.Message;
                }
            }
        }

        /// <summary>
        /// Copy a directory from old path to new path 
        /// </summary>
        /// <param name="path"></param>
        /// <param name="newPath"></param>
        /// <returns></returns>
        public override string CopyDirectory(string path, string newPath)
        {
            string documentLibraryName = GetDocumentLibraryName(path);
            string relativePath = ProcessRelativePath(path);
            string relativeNewPath = ProcessRelativePath(newPath);
            if (relativePath.EndsWith("/"))
                relativePath = relativePath.TrimEnd('/');
            if (relativeNewPath.EndsWith("/"))
                relativeNewPath = relativeNewPath.TrimEnd('/');
            string folderName = relativePath.Substring(relativePath.LastIndexOf('/') + 1);
            string relativeParentPath = relativePath.Substring(0, relativePath.LastIndexOf('/') + 1);
            string relativeParentNewPath = relativeNewPath.Substring(0, relativeNewPath.LastIndexOf('/') + 1);
            // check whether copied to same path or not
            if (relativeParentPath == relativeParentNewPath)
                return folderName + " alread exists...";
            else
            {
                try
                {
                    SharePointContorller.SPCopyFolderLocal(documentLibraryName, DocumentLibraryDictionary[documentLibraryName], folderName, SharePointUrl + relativeParentPath, SharePointUrl + relativeParentNewPath);
                    return string.Empty;
                }
                catch (Exception ex)
                {
                    return "CopyDirectory failed " + ex.Message;
                }
            }
        }

        public override DirectoryItem ResolveDirectory(string path)
        {
            DirectoryItem directoryItem = new DirectoryItem(
                    GetDirectoryName(path),
                    GetDirectoryPath(path),
                    path,
                    string.Empty,
                    GetPermissions(),
                    GetChildFiles(path),  // files are added in ResolveDirectory
                    null
                );
            return directoryItem;
        }

        /// <summary>
        /// Get all files under a specific path
        /// </summary>
        /// <param name="path"></param>
        /// <returns></returns>
        private FileItem[] GetChildFiles(string path)
        {
            List<FileItem> fileItemList = new List<FileItem>();
            List<SharePoint.FileInfo> fileInfoList = new List<SharePoint.FileInfo>();
            // Prepare parameters
            string documentLibrary = GetDocumentLibraryName(path);
            fileInfoList = SharePointContorller.SPGetFileInfos(documentLibrary, DocumentLibraryDictionary[documentLibrary], ProcessRelativePath(path));
            foreach (var fileInfo in fileInfoList)
            {
                FileItem fileItem = new FileItem(
                    fileInfo.FileName,
                    fileInfo.FileExtension,
                    fileInfo.FileSize,
                    fileInfo.FileFakeRelativePath, // here need to pay attention
                    GetItemUrl(SharePointUrl + "|||" + fileInfo.FileRelativePath), // here need to pay attention sharepointurl|||filerelativepath                                                                               
                    string.Empty,
                    GetPermissions()); // users always have rights to delete a file
                fileItem.Attributes.Add("ModificationDate", fileInfo.FileLastModificationTime);
                fileItem.Attributes.Add("OwnerName", fileInfo.FileAuthor);
                fileItemList.Add(fileItem);
            }
            return fileItemList.ToArray();
        }

        /// <summary>
        /// Used to display picture on the fly 
        /// </summary>
        /// <param name="path"></param>
        /// <returns></returns>
        private string GetItemUrl(string path)
        {
            // path|||username|||password|||domain
            PortalSecurity portalSecurity = new PortalSecurity();
            path += "|||" + portalSecurity.Decrypt(Host.GUID,EncryptSPUserName) + "|||" + portalSecurity.Decrypt(Host.GUID,EncryptSPPassword) + 
                "|||" + SessionedSPDomain;
            string escapedPath = Context.Server.UrlEncode(path);
            return string.Format("{0}?path={1}", ItemHandlerPath, escapedPath);
        }

        public override DirectoryItem ResolveRootDirectoryAsTree(string path)
        {
            DirectoryItem directoryItem = new DirectoryItem(
                    GetDirectoryName(path),
                    GetDirectoryPath(path),
                    path,
                    string.Empty,
                    GetPermissions(),
                    null,
                    GetChildDirectories(path) //Direcotries are added in ResolveRootDirectoryAsTree
                );
            return directoryItem;
        }

        /// <summary>
        /// Get child directories under a specific path
        /// </summary>
        /// <param name="path"></param>
        /// <returns></returns>
        private DirectoryItem[] GetChildDirectories(string path)
        {
            List<DirectoryItem> directoryItems = new List<DirectoryItem>();
            // Prepare parameters
            string documentLibrary = GetDocumentLibraryName(path);
            //List<string> directoryNames = SharePointContorller.SPGetFolders(documentLibrary, DocumentLibraryDictionary[documentLibrary], ProcessRelativePath(path));
            List<FolderInfo> directoryInfos = SharePointContorller.SPGetFolderInfos(documentLibrary, DocumentLibraryDictionary[documentLibrary], ProcessRelativePath(path));
            foreach (var directoryInfo in directoryInfos)
            {
                string fullPath = path + "/" + directoryInfo.FolderName;
                DirectoryItem directoryItem = new DirectoryItem(
                    directoryInfo.FolderName,
                    path,
                    fullPath,
                    string.Empty,
                    GetPermissions(),
                    null,
                    null);
                directoryItem.Attributes.Add("ModificationDate", directoryInfo.FolderLastModificationTime);
                directoryItem.Attributes.Add("OwnerName", directoryInfo.FolderAuthor);
                directoryItems.Add(directoryItem);
            }
            return directoryItems.ToArray();
        }

        /// <summary>
        /// If it is a document library, then we do not have rights to delete and rename it.
        /// But if it is a normal folder under a document library, then we have all rights to
        /// deal with it
        /// </summary>
        /// <param name="path"></param>
        /// <returns></returns>
        private PathPermissions GetPermissions()
        {
            return PathPermissions.Read | PathPermissions.Upload | PathPermissions.Delete;
        }

        private string GetDirectoryPath(string path)
        {
            return path.Substring(0, path.LastIndexOf('/') + 1);
        }

        private string GetDirectoryName(string path)
        {
            return path.Substring(path.LastIndexOf('/') + 1);
        }

        /// <summary>
        /// It is not sure how to use this method at this moment
        /// </summary>
        /// <param name="bitmap"></param>
        /// <param name="url"></param>
        /// <param name="format"></param>
        /// <returns></returns>
        public override string StoreBitmap(Bitmap bitmap, string url, ImageFormat format)
        {
            return "It is not implemented yet...";
        }

        /// <summary>
        /// Store uploaded file
        /// </summary>
        /// <param name="file"></param>
        /// <param name="path"></param>
        /// <param name="name"></param>
        /// <param name="arguments"></param>
        /// <returns></returns>
        public override string StoreFile(Telerik.Web.UI.UploadedFile file, string path, string name, params string[] arguments)
        {
            // should check whether file exists or not?
            string processedRelativePath = ProcessRelativePath(path);
            try
            {
                SharePointContorller.SPUploadFile((SharePointUrl + processedRelativePath), name, file.InputStream);
                return string.Empty;
            }
            catch (Exception ex)
            {
                return "StoreFile failed " + ex.Message;
            }
        }

        /// <summary>
        /// Enable to create new directory
        /// </summary>
        public override bool CanCreateDirectory
        {
            get
            {
                return true;
            }
        }
    }
}